package nl.boelen.game.client;

import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.Random;
import java.util.UUID;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import nl.boelen.game.client.connection.ClientPacketHandler;
import nl.boelen.game.client.connection.ClientTCPReceiver;
import nl.boelen.game.client.connection.ClientTCPSender;
import nl.boelen.game.client.connection.ClientUDP;
import nl.boelen.game.entities.Player;
import nl.boelen.game.entities.PlayerMP;
import nl.boelen.game.gfx.Colors;
import nl.boelen.game.gfx.Font;
import nl.boelen.game.gfx.Screen;
import nl.boelen.game.gfx.SpriteSheet;
import nl.boelen.game.handler.KeyboardHandler;
import nl.boelen.game.handler.MouseHandler;
import nl.boelen.game.handler.WindowHandler;
import nl.boelen.game.level.Level;
import nl.boelen.game.net.packet.Packet10Join;
import nl.boelen.game.options.Option;
import nl.boelen.game.utils.Utils;

@SuppressWarnings("serial")
public class Client extends Canvas implements Runnable {
    public static final Dimension DIMENSION = new Dimension(Option.ClientWIDTH * Option.ClientSCALE, Option.ClientHEIGHT
            * Option.ClientSCALE);
    private BufferedImage image = new BufferedImage(Option.ClientWIDTH, Option.ClientHEIGHT, BufferedImage.TYPE_INT_RGB);
    public KeyboardHandler keyboard = null;
    public MouseHandler mouse = null;
    public WindowHandler windowHandler = null;
    public JFrame frame = null;

    private Thread thread = null;
    private ClientTCPReceiver TCPReceiver = null;
    public Screen screen = null;
    public ClientUDP UDP = null;
    public ClientTCPSender TCPSender = null;
    public Level level = null;
    public Player player = null;

    private int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
    private int[] colors = new int[10 * 10 * 10];
    private int runningTimeInSec = 0;

    public Font font = new Font() {
        @Override
        public void renderChar(int x, int y, int tileIndex, int color, int mirrorDir, int scale) {
            screen.render(x, y, tileIndex, color, mirrorDir, scale);
        }
    };
    
    private boolean running = false;
    private boolean limitRender = true;
    public boolean serverOnline = true;
    public Player playerHovered;

    public Client() {
    }

    public void init() {
        int index = 0;
        for (int r = 0; r < 6; r++) {
            for (int g = 0; g < 6; g++) {
                for (int b = 0; b < 6; b++) {
                    int rr = r * 255 / 5;
                    int gg = g * 255 / 5;
                    int bb = b * 255 / 5;
                    colors[index++] = rr << 16 | gg << 8 | bb;
                }
            }
        }

        screen = new Screen(this, Option.ClientWIDTH, Option.ClientHEIGHT, new SpriteSheet("/spritesheet.png"));
        keyboard = new KeyboardHandler();
        mouse = new MouseHandler();
        this.addKeyListener(keyboard);
        this.addMouseListener(mouse);
        this.addMouseMotionListener(mouse);

        level = new Level(this, "/levels/bigtest.png");
        // this generates the level with random tiles
        // level = new Level(this);

        Random r = new Random();
        int shirtColor = (r.nextInt(6) * 100 + r.nextInt(6) * 10 + r.nextInt(6));
        player = new PlayerMP(this, 0, 0, keyboard, mouse, JOptionPane.showInputDialog(this, "Please enter a username"), null, -1, UUID
                .randomUUID().toString(), shirtColor, Option.DOWN);
        level.addEntity(player);

        Packet10Join loginPacket = new Packet10Join(player.getUserName(), player.getUniqueKey(), player.getShirtColor(), player.x,
                player.y, TCPReceiver.getPort(), UDP.getUDPPort(), Option.DOWN, player.getHealthObject().getMaxHP(), player.getHealthObject().getHP());
        TCPSender.sendPacket(loginPacket);
    }

    public synchronized void start() {
        Utils.LOG("Started Client");
        running = true;
        thread = new Thread(this);
        thread.start();

        TCPSender = new ClientTCPSender(this, Option.ServerIp, 2222);
        ClientPacketHandler packetHandler = new ClientPacketHandler(this, TCPSender);
        UDP = new ClientUDP(packetHandler, Option.ServerIp);
        TCPReceiver = new ClientTCPReceiver(packetHandler);

        UDP.start();
        TCPReceiver.start();
    }

    public synchronized void stop() {
        running = false;

        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        long lastTime = System.nanoTime();
        long clock = System.currentTimeMillis();

        double NANO = 1000000000D;
        double FRAMELIMIT = 60D;

        double nsPerTick = NANO / FRAMELIMIT;
        double nsUpdateCounter = 0;

        init();

        // Game loop
        while (running) {
            long now = System.nanoTime();
            nsUpdateCounter += (now - lastTime) / nsPerTick;
            lastTime = now;
            boolean shouldRender = !limitRender;

            // Limit the overload of rendering
            if (shouldRender) {
                limitRendering();
            }

            while (nsUpdateCounter >= 1) {
                tick();
                nsUpdateCounter -= 1;
                shouldRender = true;
            }

            if (shouldRender) {
                render();
            }

            if (System.currentTimeMillis() - clock > 1000) {
                clock += 1000;
                runningTimeInSec += 1;
            }
        }
    }

    public String getRunningTime() {
        int time = runningTimeInSec;
        int h = (time / (60 * 60));
        time -= (h * (60 * 60));
        int m = (time / 60);
        time -= (m * 60);
        int s = time;

        return "[" + h + ":" + (m >= 10 ? m : "0" + m) + ":" + (s >= 10 ? s : "0" + s) + "]";
    }

    private void limitRendering() {
        try {
            Thread.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void tick() {
        level.tick();
    }

    public void render() {
        BufferStrategy bs = getBufferStrategy();
        if (bs == null) {
            createBufferStrategy(3);
            return;
        }
        int xOffset = player.x - (screen.width / 2);
        int yOffset = player.y - (screen.height / 2);

        level.renderTiles(screen, xOffset, yOffset);
        level.renderEntities(screen, player);
        level.renderConnection(screen);

        for (int y = 0; y < screen.height; y++) {
            for (int x = 0; x < screen.width; x++) {
                int colorCode = screen.pixels[x + y * screen.width];
                if (colorCode < 255) {
                    pixels[x + y * Option.ClientWIDTH] = colors[colorCode];
                }
            }
        }

        // Shows all colors in the screen
        // showColors();

        Graphics g = bs.getDrawGraphics();
        g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
        g.dispose();

        bs.show();
    }

    public static enum DebugLevel {
        INFO, WARNING, SEVERE;
    }

    public void debug(DebugLevel level, String msg) {
        switch (level) {
            default :
            case INFO :
                Utils.LOG(msg);
                break;
            case WARNING :
                Utils.LOG("[WARNING]" + msg);
                break;
            case SEVERE :
                Utils.LOG("[SEVERE]" + msg);
                break;
        }
    }

    // For checking purposes
    public void showColors() {
        int[] colorss = new int[6 * 6 * 6];
        int i = 0;
        for (int r = 0; r < 6; r++) {
            for (int g = 0; g < 6; g++) {
                for (int b = 0; b < 6; b++) {
                    colorss[i] = r * 100 + g * 10 + b;
                    i++;
                }
            }
        }
        i = 0;
        int w = 0;
        int h = 0;
        outerloop : for (int y = 0; y < screen.height; y++) {
            for (int x = 0; x < screen.width; x++) {
                if (i >= colorss.length) {
                    break outerloop;
                }
                int colorCode = Colors.get(colorss[i]);
                pixels[x + y * Option.ClientWIDTH] = colors[colorCode];
                if (w < 2) {
                    w++;
                } else {
                    i++;
                    w = 0;
                    if (i % 6 == 0) {
                        if (h < 2) {
                            h++;
                            i -= 6;
                        } else {
                            h = 0;
                        }
                        break;
                    }
                }
            }
        }
    }
}
